#include <apr_md5.h>
#include <apr_sha1.h>
#include <apr_md4.h>

#include "common.h"

static void bytestohex(unsigned char *in, int len, char *out);

/** Return the MD5 hash for the provided string
 * \luaparam input the input string to hash
 * \luaparam raw an optional boolean - set to true to return the hash as raw bytes instead of a hex string
 * \luareturn the hash as raw types or hex string
 */
static int lua_apr_md5(lua_State *L) {
    apr_status_t rv;
    apr_size_t input_len;
    const char *input = luaL_checklstring(L, 1, &input_len);
    int raw = lua_toboolean(L, 2);
    unsigned char digest[APR_MD5_DIGESTSIZE];

    rv = apr_md5(digest, input, input_len);
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "Could not generate MD5 hash");
    }
    
    if(raw) {
        lua_pushstring(L, (char *) digest);
    } else {
        char hexval[APR_MD5_DIGESTSIZE * 2 + 1];
        bytestohex(digest, APR_MD5_DIGESTSIZE, hexval);
        lua_pushstring(L, hexval);
    }

    return 1;
}

/** Return the SHA1 hash for the provided string
 * \luaparam input the input string to hash
 * \luaparam raw an optional boolean - set to true to return the hash as raw bytes instead of a hex string
 * \luareturn the hash as raw types or hex string
 */
static int lua_apr_sha1(lua_State *L) {
    apr_size_t input_len;
    const char *input = luaL_checklstring(L, 1, &input_len);
    int raw = lua_toboolean(L, 2);
    unsigned char digest[APR_SHA1_DIGESTSIZE];
    apr_sha1_ctx_t ctx;
    
    apr_sha1_init(&ctx);
    apr_sha1_update(&ctx, input, input_len);
    apr_sha1_final(digest, &ctx);
    
    if(raw) {
        lua_pushstring(L, (char *) digest);
    } else {
        char hexval[APR_SHA1_DIGESTSIZE * 2 + 1];
        bytestohex(digest, APR_SHA1_DIGESTSIZE, hexval);
        lua_pushstring(L, hexval);
    }

    return 1;
}

/** Return the MD4 hash for the provided string
 * \luaparam input the input string to hash
 * \luaparam raw an optional boolean - set to true to return the hash as raw bytes instead of a hex string
 * \luareturn the hash as raw types or hex string
 */
static int lua_apr_md4(lua_State *L) {
    apr_status_t rv;
    apr_size_t input_len;
    const char *input = luaL_checklstring(L, 1, &input_len);
    int raw = lua_toboolean(L, 2);
    unsigned char digest[APR_MD4_DIGESTSIZE];
    
    rv = apr_md4(digest, (unsigned char *) input, input_len);
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "Could not generate MD4 hash");
    }
    
    if(raw) {
        lua_pushstring(L, (char *) digest);
    } else {
        char hexval[APR_MD4_DIGESTSIZE * 2 + 1];
        bytestohex(digest, APR_MD4_DIGESTSIZE, hexval);
        lua_pushstring(L, hexval);
    }
    
    return 1;
}

/** Helper function to convert bytestrings to hex
 * \param in the raw bytestring to convert
 * \param len the length of the string (null characters may be included in in)
 * \param out the result will be put here - len * 2 + 1 should be allocated for this string
 */
static void bytestohex(unsigned char *in, int len, char *out) {
    int i;
    for(i=0; i<len; i++) {
        sprintf(out, "%02x", in[i]);
        out += 2;
    }
    *out = '\0';
}

static const struct luaL_reg lua_apr_hash [] = {
    {"md5", lua_apr_md5},
    {"sha1", lua_apr_sha1},
    {"md4", lua_apr_md4},
    {NULL, NULL}
};

int luaopen_apr_hash(lua_State *L) {
    luaL_register(L, "apr.hash", lua_apr_hash);
    return 1;
}
